package edu.mse.camel.client.posterboard.ui.handlers;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.change.ChangeDescription;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.swt.widgets.Display;

import edu.mse.camel.client.core.events.model.ModelRewind;
import edu.mse.camel.client.core.events.model.ModelUpdateEvent2;
import edu.mse.camel.client.core.events.model.PlaybackPost;
import edu.mse.camel.client.core.events.model.ModelUpdateEvent2.KIND;
import edu.mse.camel.client.eventbus.pubsub.ISubscriber;
import edu.mse.camel.client.events.IEvent;
import edu.mse.camel.client.log.Debug;
import edu.mse.camel.client.posterboard.ui.PosterboardPlugin;
import edu.mse.camel.client.posterboard.ui.editor.CamelModelUpdateSerialNumber;
import edu.mse.camel.client.posterboard.ui.editor.PosterBoardModelManager;
import edu.mse.camel.client.posterboard.ui.emflistener.CamelChangeRecorder2;
import edu.mse.camel.client.posterboard.ui.emflistener.UMLChangeRecorder;
import edu.mse.camel.client.session.team.TeamList;

public class ModelUpdateEventHandler implements ISubscriber {

	private PosterBoardModelManager targetModelManager;
	private CamelModelUpdateSerialNumber serialNumber;

	private Stack<ChangeDescription> undoStack = new Stack<ChangeDescription>();
	UMLChangeRecorder newChangeRecorder = null;
	
	
	public ModelUpdateEventHandler(PosterBoardModelManager modelManager, CamelModelUpdateSerialNumber serialNumber ) {
		this.targetModelManager = modelManager;
		this.serialNumber = serialNumber;
	}
	
	private void beginCacheNotificationForAll(CamelNotificationRecorder recorder) {
		// the resourseSet itself is also a notifier
		ResourceSet root = targetModelManager.getResourceSet();
		int sumOriginalAdapters = 0;
		sumOriginalAdapters = recorder.install(root);
		for(Resource res : root.getResources()) {
			TreeIterator<EObject> tree = 
				EcoreUtil.getAllContents(Collections.singleton(res));
			while (tree.hasNext()) {
				Object obj = tree.next();
				if(obj instanceof Notifier) {
					Notifier notifier = (Notifier) obj;
					sumOriginalAdapters += recorder.install(notifier);
				}
			}
		}
		System.out.println("Original adapters : " + sumOriginalAdapters);
	}
		
	private void setDelivery(boolean deliver) {
		// the resourseSet itself is also a notifier
		ResourceSet root = targetModelManager.getResourceSet();
		root.eSetDeliver(deliver);
		for(Resource res : root.getResources()) {
			TreeIterator<EObject> tree = 
				EcoreUtil.getAllContents(Collections.singleton(res));
			while (tree.hasNext()) {
				Object obj = tree.next();
				if(obj instanceof Notifier) {
					Notifier notifier = (Notifier) obj;
					notifier.eSetDeliver(deliver);
				}
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see edu.mse.camel.client.eventbus.pubsub.ISubscriber#notifyEvent(edu.mse.camel.client.events.IEvent)
	 * 
	 * This method is synchronized because it may be called from the EventManager
	 * at two points:
	 * 1. While it is publishing the cached events.
	 * 2. Or during normal operation.
	 * 
	 * We don't synchronize other methods since they are all private. However, we must make sure
	 * that the model is modified from here only!
	 * TODO: Keep this in mind when implementing the role change feature.
	 * 
	 */
	@SuppressWarnings("unchecked")
	@Override
	public synchronized void notifyEvent(final IEvent e) {
		if (e instanceof ModelUpdateEvent2) {
			final ModelUpdateEvent2 me2 = (ModelUpdateEvent2) e;
			
			if(me2.getKind() != KIND.UML) {
				// this event is not for us
				return;
			}
			
			Debug.log_info(PosterboardPlugin.PLUGIN_ID, "received cmd " + me2.getCmdName() + " [" + me2.getSerialNumber() + "]");
			if(me2.getSerialNumber() != this.serialNumber.getSerialNumberAndThenInc()) {
				Debug.log_error(PosterboardPlugin.PLUGIN_ID, "serial number mistmatch. was expecting " + (this.serialNumber.getSerialNumber()-1));
				//throw new RuntimeException ("notifyEvent : serialnumber mismatch");
			}
			
			ByteArrayInputStream bais = new ByteArrayInputStream(me2.getByteArray());
			
			try {
				final ResourceSet rSet = targetModelManager.getResourceSet();
				Resource res = rSet.createResource(rSet.getResources().get(1).getURI());
				
				Map option = new HashMap();
				option.put(XMLResource.OPTION_PROCESS_DANGLING_HREF,
						XMLResource.OPTION_PROCESS_DANGLING_HREF_THROW);
				res.load(bais, option);
				
				final ChangeDescription cd = (ChangeDescription) res.getContents().get(0);
				
				Display.getDefault().syncExec(new Runnable() {
					
					@Override
					public void run() {
						CamelNotificationRecorder recorder = new CamelNotificationRecorder();
						try {
						
							if(newChangeRecorder == null) {
								if(TeamList.getInstance().isPlayback()) {
									newChangeRecorder = new UMLChangeRecorder();
									newChangeRecorder.beginRecording(Collections.singleton(rSet));
								}
							}
							
							//setDelivery(false);
							Debug.log_info(PosterboardPlugin.PLUGIN_ID, "Attaching notification recorder");
							beginCacheNotificationForAll(recorder);
							
							Debug.log_info(PosterboardPlugin.PLUGIN_ID, "Applying the change");
							cd.apply();
							//cd.applyAndReverse();
							//undoStack.push(cd);

							Debug.log_info(PosterboardPlugin.PLUGIN_ID, "Applying notifications");
							recorder.applyAndUninstallForAll();
							Debug.log_info(PosterboardPlugin.PLUGIN_ID, "Change done");
							//setDelivery(true);
							
							if(newChangeRecorder != null) {
								undoStack.push(newChangeRecorder.endRecording());
								newChangeRecorder.dispose();
								newChangeRecorder = null;
							}

						} catch (Exception e) {
							Debug.log_warning(PosterboardPlugin.PLUGIN_ID, "Failed to apply on target");
							recorder.applyAndUninstallForAll();
							if (newChangeRecorder != null) {
								newChangeRecorder.dispose();
								newChangeRecorder = null;
							}
							e.printStackTrace();
						}
					}
					
				});
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
		else if (e instanceof PlaybackPost) {
			/*
			if(newChangeRecorder != null) {
				undoStack.push(newChangeRecorder.endRecording());
				newChangeRecorder = null;
			}
			*/
		}
		else if (e instanceof ModelRewind) {
			if(undoStack.size() <= 0) {
				Debug.log_warning(PosterboardPlugin.PLUGIN_ID, "Undo stack already empty");
				return;
			}
			Display.getDefault().syncExec(new Runnable() {
				
				@Override
				public void run() {
					try {
						ChangeDescription cd = undoStack.pop();
						CamelNotificationRecorder recorder = new CamelNotificationRecorder();
						//setDelivery(false);
						Debug.log_info(PosterboardPlugin.PLUGIN_ID, "Attaching notification recorder");
						beginCacheNotificationForAll(recorder);
						Debug.log_info(PosterboardPlugin.PLUGIN_ID, "Applying the change");
						cd.apply();
						Debug.log_info(PosterboardPlugin.PLUGIN_ID, "Applying notifications");
						recorder.applyAndUninstallForAll();
						Debug.log_info(PosterboardPlugin.PLUGIN_ID, "Change done");
						//setDelivery(true);
					} catch (Exception e) {
						Debug.log_warning(PosterboardPlugin.PLUGIN_ID, "Failed to apply on target");
						e.printStackTrace();
					}
				}
				
			});
		}
	}

}
